using Mono.Cecil;
using Mono.Cecil.Cil;

namespace Cil.Ast;

public class IlAstParser : IlAstParserBase
{
    private Stack<Expression> _stack = new();

    private Expression PushToStack(SingleInstructionExpression node)
    {
        _stack.Push(node);
        return node;
    }

    public Expression Parse(Context context, MethodDefinition method, Instruction start, Instruction? end = null)
    {
        var current = start;
        var last = end == null ? null : end.Next;
        var parsedNodes = new List<Expression>();

        // While we have instruction and instruction is not covered yet
        while (current != last && current != null)
        {
            switch (current.OpCode.Code)
            {
                case Code.Nop:
                {
                    current = current.Next;
                    break;
                }

                #region Loading

                /* Parameter -> Stack */

                case Code.Ldarg:
                case Code.Ldarga:
                {
                    PushToStack(global::Cil.Ast.IlAst.ParameterGet(context, current, (ParameterDefinition)current.Operand));
                    current = current.Next;
                    break;
                }

                // 推入字段
                case Code.Ldfld:
                {
                    var instance = _stack.Pop();
                    var field = (FieldReference)current.Operand;
                    PushToStack(global::Cil.Ast.IlAst.FieldGet(context, current, field, instance));
                    current = current.Next;
                    break;
                }
                // 推入静态字段
                case Code.Ldsfld:
                {
                    var field = (FieldReference)current.Operand;
                    PushToStack(global::Cil.Ast.IlAst.FieldGet(context, current, field));
                    current = current.Next;
                    break;
                }

                // 推入字段引用地址
                case Code.Ldflda:
                {
                    var field = (FieldReference)current.Operand;
                    PushToStack(global::Cil.Ast.IlAst.FieldGetRef(context, current, field));
                    current = current.Next;
                    break;
                }
                // 推入静态字段引用地址
                case Code.Ldsflda:
                {
                    var instance = _stack.Pop();
                    var field = (FieldReference)current.Operand;
                    PushToStack(global::Cil.Ast.IlAst.FieldGetRef(context, current, field, instance));
                    current = current.Next;
                    break;
                }

                // 推入int常量
                case Code.Ldc_I4:
                {
                    PushToStack(global::Cil.Ast.IlAst.Push(context, current, Convert.ToInt32(current.Operand)));
                    current = current.Next;
                    break;
                }
                // 推入long常量
                case Code.Ldc_I8:
                {
                    PushToStack(global::Cil.Ast.IlAst.Push(context, current, Convert.ToInt64(current.Operand)));
                    current = current.Next;
                    break;
                }
                // 推入float常量
                case Code.Ldc_R4:
                {
                    PushToStack(global::Cil.Ast.IlAst.Push(context, current, Convert.ToSingle(current.Operand)));
                    current = current.Next;
                    break;
                }
                // 推入double常量
                case Code.Ldc_R8:
                {
                    PushToStack(global::Cil.Ast.IlAst.Push(context, current, Convert.ToDouble(current.Operand)));
                    current = current.Next;
                    break;
                }
                // 推入string常量
                case Code.Ldstr:
                {
                    PushToStack(global::Cil.Ast.IlAst.Push(context, current, (string)current.Operand));
                    current = current.Next;
                    break;
                }
                // 推入局部变量
                case Code.Ldloc:
                case Code.Ldloca:
                {
                    PushToStack(global::Cil.Ast.IlAst.VariableGet(context, current, (VariableReference)current.Operand));
                    current = current.Next;
                    break;
                }
                /* null -> Stack */
                case Code.Ldnull:
                {
                    PushToStack(global::Cil.Ast.IlAst.PushNull(context, current));
                    current = current.Next;
                    break;
                }
                // 从计算堆栈顶部弹出当前值，并将其存储在指定索引处的局部变量列表中。
                case Code.Stloc:
                {
                    var variable = (VariableReference)current.Operand;
                    Expression value;
                    if (!_stack.Any())
                    {
                        var catchBlock = context.UserLocals.FirstOrDefault(kv => (kv.Value == current)).Key;
                        if (catchBlock != null)
                        {
                            value = new CatchPushExpression(context, variable.VariableType);
                        }
                        else
                        {
                            throw new InvalidOperationException("Trying to get variable for catch, but no UserLocals key found");
                        }
                    }
                    else
                    {
                        value = _stack.Pop();
                    }

                    parsedNodes.Add(global::Cil.Ast.IlAst.VariableSet(context, current, (VariableReference)current.Operand, value));
                    current = current.Next;
                    break;
                }

                /* new SomeType( ... ) */
                case Code.Newobj:
                {
                    var ctorMethod = (MethodReference)current.Operand;
                    var methodParams = ctorMethod.Parameters.Select(t => _stack.Pop()).ToList();
                    methodParams.Reverse();
                    PushToStack(global::Cil.Ast.IlAst.CreateObject(context, current, ctorMethod, methodParams.ToArray()));

                    current = current.Next;
                    break;
                }

                /* new array[] { } */
                case Code.Newarr:
                {
                    var type = (TypeReference)current.Operand;
                    var length = _stack.Pop();
                    PushToStack(global::Cil.Ast.IlAst.CreateArray(context, current, type, length));
                    current = current.Next;
                    break;
                }

                /* array.Length */
                case Code.Ldlen:
                {
                    var type = (TypeReference)current.Operand;
                    var array = _stack.Pop();
                    PushToStack(global::Cil.Ast.IlAst.ArrayLength(context, current, type, array));
                    current = current.Next;
                    break;
                }

                #endregion

                #region branching

                case Code.Brfalse: // if false, 0, null
                case Code.Brtrue: // if true, <>0, <>null
                {
                    var condition = new ConditionExpression(context, _stack.Pop());
                    parsedNodes.Add(condition);
                    current = current.Next;
                    break;
                }

                case Code.Br: // Unconditional
                {
                    Console.WriteLine("Uncovered unconditional Branch");
                    current = current.Next;
                    break;
                }
                case Code.Beq: // if 2 values equal
                case Code.Bge: // if first >= second
                case Code.Bgt: // if first > second
                case Code.Ble: // if first <= second
                case Code.Blt: // if first < second
                case Code.Bne_Un: // if unsigned1 != unsigned2
                case Code.Bge_Un: // if unsigned >= unsigned2
                case Code.Bgt_Un: // if unsigned > unsigned2
                case Code.Ble_Un: // if unsigned <= unsigned2
                case Code.Blt_Un: // if unsigned < unsigned2
                {
                    var right = _stack.Pop();
                    var left = _stack.Pop();
                    PushToStack(global::Cil.Ast.IlAst.SimpleExpression(context, current, left, right));
                    current = current.Next;
                    break;
                }

                #endregion

                #region calling

                case Code.Call:
                case Code.Calli:
                case Code.Callvirt:
                {
                    var callMethod = (MethodReference)current.Operand;
                    var parametersCount = callMethod.Parameters.Count;
                    var methodParams = new Expression[parametersCount];
                    for (var index = parametersCount - 1; index >= 0; index--)
                    {
                        methodParams[index] = _stack.Pop();
                    }

                    var instance = callMethod.HasThis ? _stack.Pop() : new Expression(context);
                    var call = global::Cil.Ast.IlAst.MethodCall(context, current, callMethod, instance, methodParams.Reverse().ToArray());
                    if (!call.IsFinal)
                        PushToStack(call);
                    else
                        //_solved.Add(current, exp_call);
                        parsedNodes.Add(call);
                    current = current.Next;
                    break;
                }

                #endregion

                #region Arrays

                case Code.Ldelema: // need to cast
                case Code.Ldelem_Any: // need to cast
                case Code.Ldelem_Ref: // need to cast
                case Code.Ldelem_I1:
                case Code.Ldelem_U1:
                case Code.Ldelem_I2:
                case Code.Ldelem_U2:
                case Code.Ldelem_I4:
                case Code.Ldelem_U4:
                case Code.Ldelem_I8:
                case Code.Ldelem_I:
                case Code.Ldelem_R4:
                case Code.Ldelem_R8:
                {
                    var index = _stack.Pop();
                    var array = _stack.Pop();
                    PushToStack(global::Cil.Ast.IlAst.ArrayGetItem(context, current, array, index));
                    current = current.Next;
                    break;
                }

                case Code.Stelem_I:
                case Code.Stelem_I1:
                case Code.Stelem_I2:
                case Code.Stelem_I4:
                case Code.Stelem_I8:
                case Code.Stelem_R4:
                case Code.Stelem_R8:
                case Code.Stelem_Ref:
                case Code.Stelem_Any:
                {
                    var value = _stack.Pop();
                    var index = _stack.Pop();
                    var array = _stack.Pop();
                    parsedNodes.Add(global::Cil.Ast.IlAst.ArraySetItem(context, current, array, index, value));
                    current = current.Next;
                    break;
                }

                case Code.Sizeof:
                {
                    PushToStack(global::Cil.Ast.IlAst.Sizeof(context, current));
                    current = current.Next;
                    break;
                }

                #endregion

                #region misc

                // Compare'n'push 1 or 0
                case Code.Ceq:
                case Code.Cgt:
                case Code.Cgt_Un:
                case Code.Clt:
                case Code.Clt_Un:

                // Bitwise ops
                case Code.And:
                case Code.Or:
                case Code.Xor:

                // Math ops
                case Code.Add:
                case Code.Sub:
                case Code.Mul:
                case Code.Div:
                case Code.Div_Un:
                case Code.Rem:
                case Code.Rem_Un:
                case Code.Add_Ovf:
                case Code.Add_Ovf_Un:
                case Code.Mul_Ovf:
                case Code.Mul_Ovf_Un:
                case Code.Sub_Ovf:
                case Code.Sub_Ovf_Un:

                // Bitwise shift
                case Code.Shl:
                case Code.Shr:
                case Code.Shr_Un:
                {
                    var right = _stack.Pop();
                    var left = _stack.Pop();
                    PushToStack(global::Cil.Ast.IlAst.SimpleExpression(context, current, left, right));
                    current = current.Next;
                    break;
                }

                // (int)y, checked { (uint)char_value; }
                case Code.Conv_I1:
                case Code.Conv_I2:
                case Code.Conv_I4:
                case Code.Conv_I8:
                case Code.Conv_R4:
                case Code.Conv_R8:
                case Code.Conv_U4:
                case Code.Conv_U8:
                case Code.Conv_R_Un:
                case Code.Conv_Ovf_I1_Un:
                case Code.Conv_Ovf_I2_Un:
                case Code.Conv_Ovf_I4_Un:
                case Code.Conv_Ovf_I8_Un:
                case Code.Conv_Ovf_U1_Un:
                case Code.Conv_Ovf_U2_Un:
                case Code.Conv_Ovf_U4_Un:
                case Code.Conv_Ovf_U8_Un:
                case Code.Conv_Ovf_I_Un:
                case Code.Conv_Ovf_U_Un:
                case Code.Conv_Ovf_I1:
                case Code.Conv_Ovf_U1:
                case Code.Conv_Ovf_I2:
                case Code.Conv_Ovf_U2:
                case Code.Conv_Ovf_I4:
                case Code.Conv_Ovf_U4:
                case Code.Conv_Ovf_I8:
                case Code.Conv_Ovf_U8:
                case Code.Conv_U2:
                case Code.Conv_U1:
                case Code.Conv_U:
                case Code.Conv_I:
                case Code.Conv_Ovf_I:
                case Code.Conv_Ovf_U:
                {
                    PushToStack(global::Cil.Ast.IlAst.CastValue(context, current, _stack.Pop()));
                    current = current.Next;
                    break;
                }

                // (TType)instance
                case Code.Castclass:
                {
                    PushToStack(global::Cil.Ast.IlAst.CastClass(context, current, _stack.Pop()));
                    current = current.Next;
                    break;
                }

                case Code.Ldind_I1:
                case Code.Ldind_U1:
                case Code.Ldind_I2:
                case Code.Ldind_U2:
                case Code.Ldind_I4:
                case Code.Ldind_U4:
                case Code.Ldind_I8:
                case Code.Ldind_I:
                case Code.Ldind_R4:
                case Code.Ldind_R8:
                case Code.Ldind_Ref:
                {
                    PushToStack(global::Cil.Ast.IlAst.PtrCastToPointer(current, _stack.Pop()));
                    current = current.Next;
                    break;
                }

                case Code.Stind_Ref:
                case Code.Stind_I1:
                case Code.Stind_I2:
                case Code.Stind_I4:
                case Code.Stind_I8:
                case Code.Stind_R4:
                case Code.Stind_R8:
                case Code.Stind_I:
                {
                    var @value = _stack.Pop();
                    var @address = _stack.Pop();
                    parsedNodes.Add(global::Cil.Ast.IlAst.PtrSetValue(current, @address, @value));
                    current = current.Next;
                    break;
                }
                // 推入方法指针 (IntPtr)(void *)&Console.WriteLine()
                case Code.Ldftn:
                {
                    var prtMethod = (MethodReference)current.Operand;
                    PushToStack(global::Cil.Ast.IlAst.PtrCastToMethodHandler(context, current, prtMethod));
                    current = current.Next;
                    break;
                }
                // 推入指针 *prt
                case Code.Ldobj:
                {
                    var type = (TypeReference)current.Operand;
                    var instance = _stack.Pop();
                    PushToStack(global::Cil.Ast.IlAst.PtrCastToValue(current, instance, type));
                    current = current.Next;
                    break;
                }
                // 指针变量赋值int x; *prt = x;
                case Code.Stobj:
                {
                    var type = (TypeReference)current.Operand;
                    var sourceValue = _stack.Pop();
                    var destinationValue = _stack.Pop();
                    parsedNodes.Add(global::Cil.Ast.IlAst.PtrCopyFromToValue(current, type, sourceValue, destinationValue));
                    current = current.Next;
                    break;
                }
                // 指针变量赋值 *prt = *prt;
                case Code.Cpobj:
                {
                    var type = (TypeReference)current.Operand;
                    var source = _stack.Pop();
                    var destination = _stack.Pop();
                    parsedNodes.Add(global::Cil.Ast.IlAst.PtrCopyFromToValue(current, type, source, destination));
                    current = current.Next;
                    break;
                }
                // 从当前方法返回，并将返回值（如果存在）从被调用方的计算堆栈推送到调用方的计算堆栈上。 return 
                case Code.Ret:
                {
                    if (_stack.Count > 0)
                        parsedNodes.Add(global::Cil.Ast.IlAst.Return(context, current, _stack.Pop()));
                    else
                        parsedNodes.Add(global::Cil.Ast.IlAst.Return(context, current));
                    current = current.Next;
                    break;
                }
                // 测试对象引用（O 类型）是否为特定类的实例。 instance is T
                case Code.Isinst:
                {
                    var instance = _stack.Pop();
                    PushToStack(global::Cil.Ast.IlAst.IsInstanceOf(context, current, instance));
                    current = current.Next;
                    break;
                }
                // 将元数据标记转换为其运行时表示形式，并将其推送到计算堆栈上。  typeof(..), methodof(..), fieldof(..)
                case Code.Ldtoken:
                {
                    PushToStack(global::Cil.Ast.IlAst.LoadMetadataToken(context, current));
                    current = current.Next;
                    break;
                }

                #endregion

                #region Exceptions
                    
                // 引发当前位于计算堆栈上的异常对象。
                case Code.Throw:
                {
                    var exception = _stack.Pop();
                    parsedNodes.Add(global::Cil.Ast.IlAst.Throw(context, current, exception));
                    current = current.Next;
                    break;
                }
                    
                // 将控制从异常块的 fault 或 finally 子句转移回公共语言结构 (CLI) 异常处理程序。
                case Code.Endfinally:
                {
                    // skip because we will make this block on code generation
                    // 跳过，因为我们将在代码生成时创建此块
                    current = last;
                    break;
                }
                // 将控制从异常的 filter 子句转移回公共语言结构 (CLI) 异常处理程序。
                case Code.Endfilter:
                {
                    // var filter = _stack.Pop(); // 0=exception_continue_search, 1=exception_execute_handler
                    // parsedNodes.Add(CodeDom.TcfEndFilter(current, filter));
                    // current = last;
                    throw new InvalidOperationException("Endfilter opcode found. Should be implemented.˚");
                }
                // 退出受保护的代码区域，无条件将控制转移到特定目标指令。
                case Code.Leave:
                {
                    current = last;
                    break;
                }
                // 如果值不是有限数，则引发 ArithmeticException。
                case Code.Ckfinite:
                {
                    parsedNodes.Add(global::Cil.Ast.IlAst.Ckfinite(context, current, _stack.Pop()));
                    current = current.Next;
                    break;
                }

                #endregion
                // 移除当前位于计算堆栈顶部的值。
                case Code.Pop:
                {
                    if (!_stack.Any() && context.UserLocals.ContainsValue(current))
                    {
                        current = current.Next;
                    }
                    else
                    {
                        var expr = _stack.Pop();
                        if (_stack.Count == 0)
                        {
                            parsedNodes.Add(expr);
                        }

                        current = current.Next;
                    }

                    break;
                }
                // 复制计算堆栈上当前最顶端的值，然后将副本推送到计算堆栈上。
                case Code.Dup:
                {
                    _stack.Push((_stack.Peek()));
                    current = current.Next;
                    break;
                }
                // 对一个值执行求反并将结果推送到计算堆栈上。
                case Code.Neg:
                {
                    PushToStack(global::Cil.Ast.IlAst.Neg(context, current, _stack.Pop()));
                    current = current.Next;
                    break;
                }
                // 计算堆栈顶部整数值的按位求补并将结果作为相同的类型推送到计算堆栈上。
                case Code.Not:
                {
                    PushToStack(global::Cil.Ast.IlAst.Not(context, current, _stack.Pop()));
                    current = current.Next;
                    break;
                }

                case Code.Switch:
                {
                    var branches = current.Operand as Instruction[];
                    var branchIndex = _stack.Pop();
                    parsedNodes.Add(global::Cil.Ast.IlAst.SwitchTable(current, branchIndex, branches));
                    current = current.Next;
                    break;
                }

                /* IntPtr Alloc(uint bytes_num) */
                case Code.Localloc:
                {
                    var allocsize = _stack.Pop();
                    PushToStack(global::Cil.Ast.IlAst.PtrMemAlloc(current, allocsize));
                    current = current.Next;
                    break;
                }

                /* initializes all fields of _struct_ to zeroes or nulls */
                case Code.Initobj:
                {
                    var instance = _stack.Pop();
                    var type = (TypeReference)current.Operand;
                    parsedNodes.Add(global::Cil.Ast.IlAst.InitStruct(context, current, instance, type));
                    current = current.Next;
                    break;
                }

                case Code.Starg:
                {
                    var parameter = (ParameterReference)current.Operand;
                    var value = _stack.Pop();
                    parsedNodes.Add(global::Cil.Ast.IlAst.ParameterSet(context, current, parameter, value));
                    current = current.Next;
                    break;
                }

                case Code.Stfld:
                {
                    var value = _stack.Pop();
                    var instance = _stack.Pop();
                    var field = (FieldReference)current.Operand;
                    parsedNodes.Add(global::Cil.Ast.IlAst.FieldSet(context, current, field, value, instance));
                    current = current.Next;
                    break;
                }

                case Code.Stsfld:
                {
                    var value = _stack.Pop();
                    var field = (FieldReference)current.Operand;
                    parsedNodes.Add(global::Cil.Ast.IlAst.FieldSet(context, current, field, value));
                    current = current.Next;
                    break;
                }

                case Code.Cpblk:
                {
                    var bytesToCopy = _stack.Pop();
                    var source = _stack.Pop();
                    var destination = _stack.Pop();
                    parsedNodes.Add(global::Cil.Ast.IlAst.PtrMemCopy(current, source, destination, bytesToCopy));
                    current = current.Next;
                    break;
                }

                case Code.Initblk:
                {
                    var bytesnum = _stack.Pop();
                    var initvalue = _stack.Pop();
                    var address = _stack.Pop();
                    parsedNodes.Add(global::Cil.Ast.IlAst.PtrMemInit(current, address, initvalue, bytesnum));
                    current = current.Next;
                    break;
                }

                case Code.Box:
                {
                    PushToStack(global::Cil.Ast.IlAst.Boxing(context, current, _stack.Pop()));
                    current = current.Next;
                    break;
                }

                case Code.Unbox:
                case Code.Unbox_Any:
                {
                    PushToStack(global::Cil.Ast.IlAst.Unboxing(context, current, _stack.Pop()));
                    current = current.Next;
                    break;
                }

                /* not to be represented in AST */
                case Code.Unaligned:
                case Code.Volatile:
                case Code.Readonly:
                case Code.Break:
                {
                    parsedNodes.Add(global::Cil.Ast.IlAst.SkippedInstruction(current));
                    current = current.Next;
                    break;
                }

                default:
                    Console.WriteLine("Uncovered instruction found: {0} on 0x{1:X} ", current.OpCode.Code, current.Offset);
                    //parsedNodes.Add(CodeDom.UncoveredInstruction(context, current));
                    current = current.Next;
                    break;
            }
        }

        // If stack contains resulting expression, we couldn't create group for it. Else group should be created
        // 如果堆栈包含结果表达式，则不能为其创建组。应该创建Else组
        var root = _stack.Any() ? _stack.Pop() : new GroupExpression(context);

        if (parsedNodes.Any() && !root.IsGroup)
        {
            throw new ParserStateException("Illegal app flow found: trying to push many parsed nodes into non-group node");
        }

        var group = root as GroupExpression;

        if (group != null)
        {
            foreach (var node in parsedNodes)
            {
                group.Add(node);
                node.ParentNode = group;
            }
        }

        return root;
    }
}

/*
Strange:

        Jmp,
        Arglist,
        Refanyval,
        Mkrefany,
        Refanytype,
        Tail,
        Constrained,
        Ldvirtftn,
 */